*> 100 Cool Things - http://cobolrocks.codeplex.com/
*> Copyright (c) 2009-2010
*> by Emerson Lopes (emersonlopes@gmail.com)
*>
*> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
*> documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
*> the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 
*> to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*>
*> The above copyright notice and this permission notice shall be included in all copies or substantial portions 
*> of the Software.
*>
*> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
*> TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
*> THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
*> CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
*> DEALINGS IN THE SOFTWARE.

CLASS-ID. ComputeFormula AS "InterpreterPattern.ComputeFormula".

    environment division.
        configuration section.
            repository.
                class SystemCollectionHashTable     as "System.Collections.Hashtable"
                class SystemString                  as "System.String"
                class SystemCollectionStack         as "System.Collections.Stack"
                class SystemDoubleWrapper           as "System.Double"
                class SystemObject                  as "System.Object"
                
                class ClassAddExpression            as "InterpreterPattern.AddExpression"
                class ClassSubtractExpression       as "InterpreterPattern.SubtractExpression"
                class ClassMultiplyExpression       as "InterpreterPattern.MultiplyExpression"
                class ClassDivideExpression         as "InterpreterPattern.DivideExpression"
                class ClassPowerExpression          as "InterpreterPattern.PowerExpression"
                class ClassTerminalExpression       as "InterpreterPattern.TerminalExpression"
                class ClassNonTerminalExpression    as "InterpreterPattern.NonTerminalExpression"
                class ClassContext                  as "InterpreterPattern.Context"
                
                interface IExpression               as "InterpreterPattern.IExpression"
                
                property StringNull                 as "Empty"
                property StringLength               as "Length"
                property StackCount                 as  "Count".
                
 *> Instance's data and methods
    object.
        data division.
            working-storage section.
            copy "types.book".

            01 booleanTrue    pic 1 value b"1".
            01 booleanFalse   pic 1 value b"0".
            01 expression     object reference SystemString.
            01 ctx            object reference ClassContext.            
            01 operators      object reference SystemCollectionHashTable private.
            01 tempObject     object reference SystemObject.
            
        procedure division.
        
            method-id. NEW.
             
                 procedure division.
                     invoke SystemCollectionHashTable "NEW" returning operators
                     
                     invoke operators "Add" using by value "+" by value "1"
                     invoke operators "Add" using by value "-" by value "1"
                     invoke operators "Add" using by value "/" by value "2"
                     invoke operators "Add" using by value "*" by value "2"
                     invoke operators "Add" using by value "^" by value "2"
                     invoke operators "Add" using by value "(" by value "0"

            end method NEW.
            
            method-id. SetContext     as "SetContext".
                 data division.
                     linkage section.
                     
                     01  inContext    object reference ClassContext.
                 
                 procedure division using inContext.
                 
                     set ctx                to  inContext
                                      
            end method SetContext.

            method-id. SetExpression     as "SetExpression".
                 data division.
                     linkage section.
                     
                     01  inExpression    object reference SystemString.
                 
                 procedure division using inExpression.
                 
                     set expression           to  inExpression
                                      
            end method SetExpression.
            
            method-id. EvaluateExpression as "Evaluate".
                
                data division.
                
                    working-storage section.
                    
                    01 postFixExp   object reference SystemString.
                    01 rootNode     object reference IExpression.
                    
                    linkage section.
                    
                    01 outValue     type SystemDouble.
                    
                procedure division returning outValue.
                    
                    set   postFixExp        to  self::"infixToPostFix"(expression)
                    set   rootNode          to  self::"BuildTree"(postFixExp)
                    set   outValue          to  rootNode::"Evaluate"(ctx)
                    
            end method EvaluateExpression.
    
            method-id. GetNonTerminalExpression as "GetNonTerminalExpression" private.
                
                data division.
                
                    working-storage section.
                    
                    01 postFixExp      object reference SystemString.
                    01 rootNode        object reference IExpression.
                    01 singleChar      pic x.
                    
                    linkage section.
                    
                    01 operation       object reference SystemString.
                    01 leftExpression  object reference IExpression.
                    01 rightExpression object reference IExpression.
                    01 outValue        object reference ClassNonTerminalExpression.
                    
                procedure division using operation leftExpression rightExpression returning outValue.

                    set    outValue             to  null

                    set    singleChar           to  operation::"Trim"()
                    
                    evaluate singleChar
                    
                             when "+"
                                 invoke ClassAddExpression "NEW" using leftExpression, rightExpression returning outValue
                     
                             when "-"
                                 invoke ClassSubtractExpression "NEW" using leftExpression, rightExpression returning outValue

                             when "*"
                                 invoke ClassMultiplyExpression "NEW" using leftExpression, rightExpression returning outValue

                             when "/"
                                 invoke ClassDivideExpression "NEW" using leftExpression, rightExpression returning outValue
                    
                             when "^"
                                 invoke ClassPowerExpression "NEW" using leftExpression, rightExpression returning outValue

                    end-evaluate
                                 
            end method GetNonTerminalExpression.
            
            method-id. BuildTree as "BuildTree" private.
                
                data division.
                
                    working-storage section.
                    
                    01 currentChar     object reference SystemString.
                    01 leftOperand     object reference IExpression.
                    01 rightOperand    object reference IExpression.
                    01 exp             object reference IExpression.
                    01 stack           object reference SystemCollectionStack.  
                    01 rootNode        object reference IExpression.
                    01 indexer         pic s9(09) value zeros.

                    linkage section.
                    
                    01 inFormula       object reference SystemString.
                    01 outExpression   object reference IExpression.
                    
                procedure division using inFormula returning outExpression.

                    invoke SystemCollectionStack "NEW" returning stack
                    
                    move   zeros                 to  indexer

                    perform until indexer = StringLength of inFormula
                        set  currentChar         to  inFormula::"Substring"(indexer, 1)
                        
                        if   (self::"IsOperator"(currentChar) = booleanFalse)
                             set   exp           to  ClassTerminalExpression::"NEW"(currentChar)
                             
                             invoke stack "Push" using by value exp
                        else
                             set  tempObject     to  stack::"Pop"()
                             set  rightOperand   to  tempObject as IExpression
                             set  tempObject     to  stack::"Pop"()
                             set  leftOperand    to  tempObject as IExpression
                             set  exp            to  self::"GetNonTerminalExpression"(currentChar, leftOperand, rightOperand)
                             
                             invoke stack "Push" using by value exp
                        end-if
                        
                        add  1                   to  indexer
                        
                    end-perform
                    
                    set  tempObject              to  stack::"Pop"()
                    set  outExpression           to  tempObject as IExpression

                                 
            end method BuildTree.
            
            method-id. IsOperator as "IsOperator" private.
                data division.
                
                    working-storage section.
                    01  singleChar     pic x. 
                    
                    linkage section.
                    
                    01  inCharacter    object reference SystemString.
                    01  outBool        pic 1.
                    
                procedure division using inCharacter returning outBool.
                    set   singleChar            to  inCharacter
                
                    if (singleChar = "+" or "-" or "*" or "/" or "^")
                        move  booleanTrue       to  outBool
                    else
                        move  booleanFalse      to  outBool
                    end-if
                    
            end method IsOperator.
            
            method-id. infixToPostFix as "InFixToPostFix".
                data division.
                    working-storage section.

                    01 stack           object reference SystemCollectionStack.  
                    01 pfExpr          object reference SystemString.
                    01 tempString      object reference SystemString.
                    01 expr            object reference SystemString.
                    01 indexer         type SystemInt32.
                    01 stringVal1      object reference SystemString.
                    01 stringVal2      object reference SystemString.
                    01 val1            type SystemDouble.
                    01 val2            type SystemDouble. 
                    01 currentChar     object reference SystemString.
                    
                    linkage section.
                    01 inString        object reference SystemString.
                    01 outString       object reference SystemString.
 
                procedure division using inString returning outString.
                    invoke SystemCollectionStack "NEW" returning stack
    
                    set    pfExpr              to  StringNull of SystemString
                    set    tempString          to  StringNull of SystemString
                    set    expr                to  inString::"Trim"()
                    
                    move   zeros               to  indexer

                    perform until indexer = StringLength of inString
                        set currentChar       to  inString::"Substring"(indexer, 1)
                   
                        if  (currentChar::"Equals"(" ") = booleanTrue)
                            add  1              to  indexer
                            exit to test of perform 
                        end-if

                        if (self::"IsOperator"(currentChar) = booleanFalse) and (currentChar::"Equals"("(") = booleanFalse) and (currentChar::"Equals"(")") = booleanFalse) 
                           set    pfExpr     to  SystemString::"Concat"(pfExpr, currentChar)
                        end-if
                
                        if (currentChar::"Equals"("(") = booleanTrue)
                           invoke stack "Push" using currentChar
                        end-if
                             
                        if (currentChar::"Equals"(")") = booleanTrue)
                           set  tempObject          to  stack::"Pop"() 
                           set  tempString          to  tempObject as SystemString

                           perform until tempString::"Equals"("(") = booleanTrue
                                   set  pfExpr      to  SystemString::"Concat"(pfExpr, tempString)
                                   set  tempObject  to  stack::"Pop"() 
                                   set  tempString  to  tempObject as SystemString
                           end-perform
               
                           set     tempString       to  StringNull of SystemString
                        end-if
            
                        if (self::"IsOperator"(currentChar) = booleanTrue)
                           if (StackCount of stack > 0)
                              set  tempObject       to  stack::"Pop"() 
                              set  tempString       to  tempObject as SystemString
                              set  tempObject       to  operators::"Get_Item"(tempString)
                              set  stringVal1       to  tempObject as SystemString
                              set  tempObject       to  operators::"Get_Item"(currentChar)
                              set  stringVal2       to  tempObject as SystemString
                              
                              move SystemDoubleWrapper::"Parse"(stringVal1)  to  val1
                              move SystemDoubleWrapper::"Parse"(stringVal2)  to  val2
                              
                              perform until val1 < val2
                                      set   pfExpr             to  SystemString::"Concat"(pfExpr, tempString)
                                      
                                      move  -100               to  val1
                                      
                                      if (StackCount of stack > 0)
                                         set  tempObject       to  stack::"Pop"()
                                         set  tempString       to  tempObject as SystemString 
                                         set  tempObject       to  operators::"Get_Item"(tempString) 
                                         set  stringVal1       to  tempObject as SystemString
                                         set  val1             to  SystemDoubleWrapper::"Parse"(stringVal1)
                                      end-if
                                      
                              end-perform        
                                 
                              if (val1 < val2) and (val1 > -100)
                                 invoke stack "Push" using tempString
                              end-if
                           end-if
                        
                           invoke stack "Push" using currentChar
                        end-if
                      
                        add     1             to  indexer
                    end-perform
               
                    perform until StackCount of stack = 0
                            set   tempObject  to  stack::"Pop"()
                            set   tempString  to  tempObject as SystemString
                            set   pfExpr      to  SystemString::"Concat"(pfExpr, tempString)
                    end-perform
                    
                    set     outString         to  pfExpr
               
               end method infixToPostFix.
           
    end object.
END CLASS ComputeFormula.
               
               